#include <functional>
#include <iostream>
#include <random>

#include <boost/function/function1.hpp>
#include <boost/bind/placeholders.hpp>
#include <boost/bind.hpp>

#include "benchmark.hpp"
#include "lib/KLDelegate.h"
#include "../nano_signal_slot.hpp"
#include "lib/jl_signal/FastDelegate.h"

/**
g++ del_benchmark.cpp -std=c++11 -O2 -o ~/benchmark/del_benchmark.out
*/

namespace benchmark_class
{

class Del
{
    std::size_t m_data[c_payload];

    NOINLINE(void next(Rng_t& rng))
    {
        volatile std::size_t a = rng();
        (void)a;
    }

    public:

//------------------------------------------------------------------------------

    NOINLINE(static std::intmax_t boost_function(std::size_t timer_limit))
    {
        typedef boost::function<void(Rng_t&)> function;

        std::size_t count = 0;
        Rng_t rng;
        Del a;

        g_timer.reset();
        do
        {
            function delegate = boost::bind(&Del::next, &a, _1);
            delegate(rng);
            ++count;
        }
        while (timer_limit > g_timer.count<Timer_u>());

        return timer_limit / count;
    }

//------------------------------------------------------------------------------

    NOINLINE(static std::intmax_t nano_function(std::size_t timer_limit))
    {
        typedef Nano::function<void(Rng_t&)> function;

        std::size_t count = 0;
        Rng_t rng;
        Del a;

        g_timer.reset();
        do
        {
            function delegate = function::bind<Del, &Del::next>(&a);
            delegate(rng);
            ++count;
        }
        while (timer_limit > g_timer.count<Timer_u>());

        return timer_limit / count;
    }

//------------------------------------------------------------------------------

    NOINLINE(static std::intmax_t std_function(std::size_t timer_limit))
    {
        typedef std::function<void(Rng_t&)> function;

        std::size_t count = 0;
        Rng_t rng;
        Del a;

        g_timer.reset();
        do
        {
            function delegate = std::bind(&Del::next, &a, std::placeholders::_1);
            delegate(rng);
            ++count;
        }
        while (timer_limit > g_timer.count<Timer_u>());

        return timer_limit / count;
    }

//------------------------------------------------------------------------------

    NOINLINE(static std::intmax_t kl_delegate(std::size_t timer_limit))
    {
        typedef MethodDelegate<Del, void, Rng_t&> function;

        std::size_t count = 0;
        Rng_t rng;
        Del a;

        g_timer.reset();
        do
        {
            function delegate (&a, &Del::next);
            delegate(rng);
            ++count;
        }
        while (timer_limit > g_timer.count<Timer_u>());

        return timer_limit / count;
    }

//------------------------------------------------------------------------------

    NOINLINE(static std::intmax_t f_delegate(std::size_t timer_limit))
    {
        typedef fastdelegate::FastDelegate1<Rng_t&> function;

        std::size_t count = 0;
        Rng_t rng;
        Del a;

        g_timer.reset();
        do
        {
            function delegate;
            delegate.bind(&a, &Del::next);
            delegate(rng);
            ++count;
        }
        while (timer_limit > g_timer.count<Timer_u>());

        return timer_limit / count;
    }

//------------------------------------------------------------------------------

    NOINLINE(static std::intmax_t reference(std::size_t timer_limit))
    {
        std::size_t count = 0;
        Rng_t rng;
        Del a;

        g_timer.reset();
        do
        {
            a.next(rng);
            ++count;
        }
        while (timer_limit > g_timer.count<Timer_u>());

        return timer_limit / count;
    }

//------------------------------------------------------------------------------

    NOINLINE(static std::intmax_t error(std::size_t timer_limit))
    {
        std::size_t count = 0;

        g_timer.reset();
        do
        {
            ++count;
        }
        while (timer_limit > g_timer.count<Timer_u>());

        return timer_limit / count;
    }
};

} // namespace benchmark_class -------------------------------------------------

int main(int argc, char* argv[])
{
    using namespace benchmark_class;

    if (argc == 2)
    {
        std::cerr << "[paused] sudo renice -1 `pidof del_benchmark.out`" << std::endl;
        char c = std::cin.get(); (void)c;

        auto limit = Timer_u(Limit_u(std::stoll(argv[1]))).count();

        auto error = Del::error(limit);
        auto reference = Del::reference(limit);
        reference -= std::abs(reference - error);

        std::cout << "boost::function [timing] == "
                  << std::abs(Del::boost_function(limit) - reference) << " ns" << std::endl;
        std::cout << "Nano::function [timing] == "
                  << std::abs(Del::nano_function(limit) - reference) << " ns" << std::endl;
        std::cout << "std::function [timing] == "
                  << std::abs(Del::std_function(limit) - reference) << " ns" << std::endl;
        std::cout << "KLDelegate [timing] == "
                  << std::abs(Del::kl_delegate(limit) - reference) << " ns" << std::endl;
        std::cout << "FastDelegate [timing] == "
                  << std::abs(Del::f_delegate(limit) - reference) << " ns" << std::endl;
    }
    else
    {
        std::cerr << "Must supply per-benchmark timer limit (in seconds)" << std::endl;
        return 1;
    }
}
